/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.hyperlink;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URI;
import java.util.logging.Logger;

/**
 * A implementation wrapping <code>Desktop</code> actions BROWSE and MAIL, that
 * is URI-related.
 * 
 * @author Jeanette Winzenburg
 */
public class HyperlinkAction extends AbstractHyperlinkAction<URI> {
	@SuppressWarnings("unused")
	private static final Logger LOG = Logger.getLogger(HyperlinkAction.class.getName());

	private Action desktopAction;
	private URIVisitor visitor;

	/**
	 * Factory method to create and return a HyperlinkAction for the given uri.
	 * Tries to guess the appropriate type from the uri. If uri is not null and
	 * has a scheme of mailto, create one of type Mail. In all other cases,
	 * creates one for BROWSE.
	 * 
	 * @param uri
	 *            to uri to create a HyperlinkAction for, maybe null.
	 * @return a HyperlinkAction for the given URI.
	 * @throws HeadlessException
	 *             if {@link GraphicsEnvironment#isHeadless()} returns
	 *             {@code true}
	 * @throws UnsupportedOperationException
	 *             if the current platform doesn't support Desktop
	 */
	public static HyperlinkAction createHyperlinkAction(URI uri) {
		Action type = isMailURI(uri) ? Action.MAIL : Action.BROWSE;
		return createHyperlinkAction(uri, type);
	}

	/**
	 * Creates and returns a HyperlinkAction with the given target and action
	 * type.
	 * 
	 * @param uri
	 *            the target uri, maybe null.
	 * @param desktopAction
	 *            the type of desktop action this class should perform, must be
	 *            BROWSE or MAIL
	 * @return a HyperlinkAction
	 * @throws HeadlessException
	 *             if {@link GraphicsEnvironment#isHeadless()} returns
	 *             {@code true}
	 * @throws UnsupportedOperationException
	 *             if the current platform doesn't support Desktop
	 * @throws IllegalArgumentException
	 *             if unsupported action type
	 */
	public static HyperlinkAction createHyperlinkAction(URI uri, Action type) {
		return new HyperlinkAction(uri, type);
	}

	/**
	 * @param uri
	 * @return
	 */
	private static boolean isMailURI(URI uri) {
		return uri != null && "mailto".equalsIgnoreCase(uri.getScheme());
	}

	/**
	 * Instantiates a HyperlinkAction with action type BROWSE.
	 * 
	 * @throws HeadlessException
	 *             if {@link GraphicsEnvironment#isHeadless()} returns
	 *             {@code true}
	 * @throws UnsupportedOperationException
	 *             if the current platform doesn't support Desktop
	 * @throws IllegalArgumentException
	 *             if unsupported action type
	 */
	public HyperlinkAction() {
		this(Action.BROWSE);
	}

	/**
	 * Instantiates a HyperlinkAction with the given action type.
	 * 
	 * @param desktopAction
	 *            the type of desktop action this class should perform, must be
	 *            BROWSE or MAIL
	 * @throws HeadlessException
	 *             if {@link GraphicsEnvironment#isHeadless()} returns
	 *             {@code true}
	 * @throws UnsupportedOperationException
	 *             if the current platform doesn't support Desktop
	 * @throws IllegalArgumentException
	 *             if unsupported action type
	 */
	public HyperlinkAction(Action desktopAction) {
		this(null, desktopAction);
	}

	/**
	 * 
	 * @param uri
	 *            the target uri, maybe null.
	 * @param desktopAction
	 *            the type of desktop action this class should perform, must be
	 *            BROWSE or MAIL
	 * @throws HeadlessException
	 *             if {@link GraphicsEnvironment#isHeadless()} returns
	 *             {@code true}
	 * @throws UnsupportedOperationException
	 *             if the current platform doesn't support Desktop
	 * @throws IllegalArgumentException
	 *             if unsupported action type
	 */
	public HyperlinkAction(URI uri, Action desktopAction) {
		super();
		if (!Desktop.isDesktopSupported()) {
			throw new UnsupportedOperationException("Desktop API is not " + "supported on the current platform");
		}
		if (desktopAction != Desktop.Action.BROWSE && desktopAction != Desktop.Action.MAIL) {
			throw new IllegalArgumentException("Illegal action type: " + desktopAction + ". Must be BROWSE or MAIL");
		}
		this.desktopAction = desktopAction;
		getURIVisitor();
		setTarget(uri);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * 
	 * Implemented to perform the appropriate Desktop action if supported on the
	 * current target. Sets the visited property to true if the desktop action
	 * doesn't throw an exception or to false if it did.
	 * 
	 * Does nothing if the action isn't supported.
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		if (!getURIVisitor().isEnabled(getTarget()))
			return;
		try {
			getURIVisitor().visit(getTarget());
			setVisited(true);
		} catch (IOException e1) {
			setVisited(false);
			LOG.fine("cant visit Desktop " + e);
		}
	}

	/**
	 * @return
	 */
	public Action getDesktopAction() {
		return desktopAction;
	}

	@Override
	protected void installTarget() {
		// doohh ... this is called from super's constructor before we are
		// fully initialized
		if (visitor == null)
			return;
		super.installTarget();
		updateEnabled();
	}

	/**
     * 
     */
	private void updateEnabled() {
		setEnabled(getURIVisitor().isEnabled(getTarget()));
	}

	/**
	 * @return
	 */
	private URIVisitor getURIVisitor() {
		if (visitor == null) {
			visitor = createURIVisitor();
		}
		return visitor;
	}

	/**
	 * @return
	 */
	private URIVisitor createURIVisitor() {
		return getDesktopAction() == Action.BROWSE ? new BrowseVisitor() : new MailVisitor();
	}

	/**
	 * Thin wrapper around Desktop functionality to allow uniform handling of
	 * different actions in HyperlinkAction.
	 * 
	 */
	private abstract class URIVisitor {
		protected boolean desktopSupported = Desktop.isDesktopSupported();

		/**
		 * Returns a boolean indicating whether the action is supported on the
		 * given URI. This implementation returns true if both the Desktop is
		 * generally supported and <code>isActionSupported()</code>.
		 * 
		 * PENDING JW: hmm ... which class exactly has to check for valid
		 * combination of Action and URI?
		 * 
		 * @param uri
		 * @return
		 * 
		 * @see #isActionSupported()
		 */
		public boolean isEnabled(URI uri) {
			return desktopSupported && isActionSupported();
		}

		/**
		 * Visits the given URI via Desktop functionality. Must not be called if
		 * not enabled.
		 * 
		 * @param uri
		 *            the URI to visit
		 * @throws IOException
		 *             if the Desktop method throws IOException.
		 * 
		 */
		public abstract void visit(URI uri) throws IOException;

		/**
		 * Returns a boolean indicating if the action is supported by the
		 * current Desktop.
		 * 
		 * @return true if the Action is supported by the current desktop, false
		 *         otherwise.
		 */
		protected abstract boolean isActionSupported();
	}

	private class BrowseVisitor extends URIVisitor {

		/**
		 * {@inheritDoc}
		 * <p>
		 * 
		 * Implemented to message the browse method of Desktop.
		 */
		@Override
		public void visit(URI uri) throws IOException {
			Desktop.getDesktop().browse(uri);
		}

		/**
		 * {@inheritDoc}
		 * <p>
		 * 
		 * Implemented to query the Desktop for support of BROWSE action.
		 */
		@Override
		protected boolean isActionSupported() {
			return Desktop.getDesktop().isSupported(Desktop.Action.BROWSE);
		}

		/**
		 * {@inheritDoc}
		 * <p>
		 * 
		 * Implemented to guard against null URI in addition to super.
		 */
		@Override
		public boolean isEnabled(URI uri) {
			return uri != null && super.isEnabled(uri);
		}

	}

	private class MailVisitor extends URIVisitor {

		/**
		 * {@inheritDoc}
		 * <p>
		 * 
		 * Implemented to message the mail function of Desktop.
		 */
		@Override
		public void visit(URI uri) throws IOException {
			if (uri == null) {
				Desktop.getDesktop().mail();
			} else {
				Desktop.getDesktop().mail(uri);
			}
		}

		/**
		 * {@inheritDoc}
		 * <p>
		 * 
		 * Implemented to query the Desktop for support of MAIL action.
		 */
		@Override
		protected boolean isActionSupported() {
			return Desktop.getDesktop().isSupported(Desktop.Action.MAIL);
		}

	}
}
